package com.ahua.mallchat.common.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.ahua.mallchat.common.adapter.WebSocketAdapter;
import com.ahua.mallchat.common.common.event.UserLoginEvent;
import com.ahua.mallchat.common.config.ThreadPoolConfig;
import com.ahua.mallchat.common.dao.UserDao;
import com.ahua.mallchat.common.domain.bo.WSChannelExtraBO;
import com.ahua.mallchat.common.domain.enums.user.RoleEnum;
import com.ahua.mallchat.common.domain.pojo.User;
import com.ahua.mallchat.common.domain.vo.WSBaseResp;
import com.ahua.mallchat.common.service.LoginService;
import com.ahua.mallchat.common.service.RoleService;
import com.ahua.mallchat.common.service.WebSocketService;
import com.ahua.mallchat.common.utils.NettyUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class WebSocketServiceImpl implements WebSocketService {

    /**
     * 管理所有用户的连接（登录态/游客）
     */
    private static final ConcurrentHashMap<Channel, WSChannelExtraBO> ONLINE_WS_MAP = new ConcurrentHashMap<>();

    public static final Duration DURATION = Duration.ofHours(1);
    public static final int MAXIMUM_SIZE = 1000;

    /**
     * 登陆码和channel的映射
     */
    private static final Cache<Integer, Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder().maximumSize(MAXIMUM_SIZE).expireAfterWrite(DURATION).build();

    @Resource
    private WxMpService wxMpService;

    @Resource
    private UserDao userDao;

    @Resource
    private LoginService loginService;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Resource
    private RoleService roleService;

    @Resource
    @Qualifier(ThreadPoolConfig.WS_EXECUTOR)
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    public void connect(Channel channel) {
        //目前我们存个空对象就好了，因为用户可能只是进入到聊天室，还没登陆
        //等后续登陆逻辑那里 我们再修改这个map
        ONLINE_WS_MAP.put(channel, new WSChannelExtraBO());
    }

    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel) {
        //生成随机不重复的登录码,并将channel存在本地cache中
        Integer code = randomLoginCode(channel);
        //找微信申请带参二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());
        //返回给前端（channel必在本地）
        sendMsg(channel, WebSocketAdapter.buildResp(wxMpQrCodeTicket));
    }

    @Override
    public void removeChannel(Channel channel) {
        ONLINE_WS_MAP.remove(channel);
    }

    @Override
    public void scanLoginSuccess(Integer code, Long id) {
        //要确定这个连接是我们这台机器上的
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(channel)) {
            return;
        }
        User user = userDao.getById(id);
        //移除code
        WAIT_LOGIN_MAP.invalidate(code);
        //调用登陆模块获取token
        String token = loginService.login(id);
        loginSuccess(channel, user, token);
    }

    /**
     * 握手认证
     *
     * @param channel
     * @param token
     */
    @Override
    public void authorize(Channel channel, String token) {
        //校验token是否失效
        Long uid = loginService.getValidUid(token);
        if (Objects.isNull(uid)) {
            //token 失效 给前端发送重新登陆的resp
            sendMsg(channel, WebSocketAdapter.buildInvalidateTokenResp());
        } else {
            //token 未失效 登陆成功
            User user = userDao.getById(uid);
            loginSuccess(channel, user, token);
        }
    }

    @Override
    public void sendMsgToAll(WSBaseResp<?> msg) {
        //给在线每个在线用户都发一个消息
//        ONLINE_WS_MAP.forEach((channel, ext) -> {
//            sendMsg(channel, msg);
//        });
        //为了效率，我们这里给他一个线程池的推送
        ONLINE_WS_MAP.forEach((channel, ext) -> {
            threadPoolTaskExecutor.execute(() -> sendMsg(channel, msg));
        });
    }

    private void loginSuccess(Channel channel, User user, String token) {
        //保存channel对应的用户信息
        WSChannelExtraBO wsChannelExtraBO = ONLINE_WS_MAP.get(channel);
        wsChannelExtraBO.setUid(user.getId());
        //给前端推送消息
        sendMsg(channel, WebSocketAdapter.buildLoginSuccessResp(user, token, roleService.hasPower(user.getId(), RoleEnum.CHAT_MANAGER)));
        //用户上线成功的事件
        user.setLastOptTime(new Date());
        user.refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP));
        applicationEventPublisher.publishEvent(new UserLoginEvent(this, user));
    }

    @Override
    public void waitAuthorize(Integer code) {
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(channel)) {
            return;
        }
        sendMsg(channel, WebSocketAdapter.buildWaitAuthorizeResp());
    }

    private void sendMsg(Channel channel, WSBaseResp<?> resp) {
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(resp)));
    }

    /**
     * 随机生成二维码的code值
     *
     * @param channel
     * @return
     */
    private Integer randomLoginCode(Channel channel) {
        Integer code;
        do {
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        } while (Objects.nonNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(code, channel)));

        return code;
    }
}
